package liquidfs.data.fs;

import java.io.*;
import java.util.*;

import com.thoughtworks.xstream.annotations.*;

import liquidfs.core.*;
import liquidfs.data.*;
import liquidfs.data.ex.*;
import liquidfs.data.fs.util.*;
import liquidfs.data.ops.*;

@XStreamAlias("liquid-directory")
public class LocalLiquidDirectory extends LocalLiquidObject implements LiquidDirectory {
   
   private LiquidMetadataCache metaCache = null;
   private LiquidContentsCache contentsCache = null;
   
   public LocalLiquidDirectory(LiquidFileSystem fs, String path) {
      super(fs, path);
      refresh();
   }
   
   public LiquidMetadataCache getMetadataCache() {
      return this.metaCache;
   }
   
   public LiquidContentsCache getContentsCache () {
      return this.contentsCache;
   }
   
   @Override
   public void refresh () {
      this.metaCache = new LiquidMetadataCache(this.getPath(), 0, this.getFileSystem().getMetadata(this.getPath()), true);
      this.contentsCache = null;
   }
   
   @Override
   public List<LocalLiquidObject> getLocalContents () {
      
      // Create the list
      List<LocalLiquidObject> contents = new ArrayList<LocalLiquidObject>();
        
      // Read the local files from the operating system
      File dir = this.getFileSystem().getLocalFile(this.getPath());
      File[] files = dir.listFiles();
      
      // Loop through each local file and add the corresponding LiquidObject to the list
      for (File file : files) {
         
         // Construct the proper LiquidPath out of the file's absolute path
         String relativePath = (this.getPath().equals("/") ? "/" : this.getPath() + "/") + file.getName();
         
         // Create the appropriate kind of object depending on whether the file is a directory or not
         if (file.isDirectory()) {
            contents.add(new LocalLiquidDirectory(this.getFileSystem(), relativePath));
         }
         else {
            contents.add(new LocalLiquidFile(this.getFileSystem(), relativePath));
         }
      }
      
      // return the list
      return contents;

   }

   @Override
   public List<LiquidObject> getContents() {
      
      if (this.contentsCache == null) {
         // Do this through an operation
         QueryDirectoryContentsOperation op = new QueryDirectoryContentsOperation (this.getFileSystem().getLocalDevice(), this.getPath());
         Result<List<LiquidObject>> result = this.getFileSystem().process(op, null, null);
         
         // Return the list
         this.contentsCache = new LiquidContentsCache(this.getPath(), result.getData());
      }
      
      return this.contentsCache.getContents();
   }
   
   public static void mergeContentLists(List<LiquidObject> main, List<LiquidObject> addendum) {
      
      // Loop through the addendum objects and merge them into the main list
      for (LiquidObject addendumRecord : addendum) {
         
         // A record is unique if it is only in one place (defaults to true)
         boolean unique = true;
         
         // For each remote record, check if it is the same as a local record (i.e., the file is mirrored in at least two places)
         for (LiquidObject existingRecord : main) {
            if (addendumRecord.getPath().equals(existingRecord.getPath())) {               
               unique = false;
               break;
            }
         }
         
         // If it is unique, add it to the contents
         if (unique) {
            main.add(addendumRecord);
         }
      }
   }

   @Override
   public long getSize () {
      return 0;
   }
   
   @Override
   public long getFileSize() {
      
      long totalSize = 0;
      List<LiquidObject> contents = this.getContents();
      
      for (LiquidObject object : contents) {
         totalSize += object.getSize();
      }
      
      return totalSize;
   }
   
   @Override
   public long getLocalFileSize() {
      
      long totalSize = 0;
      List<LocalLiquidObject> contents = this.getLocalContents();
      
      for (LiquidObject object : contents) {
         
         if (object.isDirectory()) {
            LiquidDirectory directory = object.asDirectory();
            totalSize += directory.getLocalFileSize();
         }
         else {
            totalSize += object.getSize();
         }
      }
      
      return totalSize;
      
   }
   
   @Override
   public Metadata getMetadata () {
      if (this.metaCache.getMetadata() == null) {
         this.refresh();
      }
      return this.metaCache.getMetadata();
   }

   @Override
   public Date getCreationDate() {
      return this.getMetadata().getCreationDate();
   }

   @Override
   public Date getModificationDate() {
      return this.getMetadata().getModificationDate();
   }

   @Override
   public Date getLastAccessedDate() {
      return this.getMetadata().getLastAccessedDate();
   }

   @Override
   public boolean isDirectory() {
      return true;
   }
   
   @Override
   public LiquidDirectory asDirectory () {
      return this;
   }
   
   @Override
   public LiquidFile asFile () {
      throw new WrongObjectTypeException("Filepath " + this.getPath() + " refers to a directory, not a file.");
   }
   
   @Override
   public String toString () {
      return (this.getPath().equals("/") ? "/" : this.getPath() + "/");
   }
   
   @Override
   public List<PeerDevice> getStorageDevices() {
      
      if (this.metaCache.getStorageDevices() == null) {
         QueryStorageDevicesOperation op = new QueryStorageDevicesOperation(this.getFileSystem().getLocalDevice(), this.getPath());
         Result<List<PeerDevice>> result = this.getFileSystem().process(op);
         this.metaCache.setStorageDevices(result.getData());
      }
      
      return this.metaCache.getStorageDevices();
   }
}
